﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace ChessGameEngine
{
    public enum PieceColor
    {
        White,
        Black
    }

    public enum Direction
    {
        North,
        West,
        South,
        East,
        NorthWest,
        NorthEast,
        SouthEast,
        SouthWest
    }

    public class ChessEngine
    {
        public const int MaxTiles = 8;

        private TilePosition selectedTilePosition;
        private readonly TilePosition newPiecePosition;
        private readonly double boardWidth;
        private readonly double boardHeight;
        private readonly TileState[,] boardState;
        private IEnumerable<TilePosition> possibleMoves;

        public ChessEngine(double boardWidth, double boardHeight)
        {
            this.PlayerColor = PieceColor.White;
            this.boardWidth = boardWidth;
            this.boardHeight = boardHeight;

            TileWidth = (int)boardWidth / MaxTiles;
            TileHeight = (int)boardHeight / MaxTiles;

            boardState = new TileState[MaxTiles, MaxTiles];
            selectedTilePosition = newPiecePosition = TilePosition.Default;
        }

        public int TileWidth { get; private set; }
        public int TileHeight { get; private set; }

        public PieceColor PlayerColor { get; set; }
        public TileState[,] BoardState
        {
            get { return boardState; }
        }

        public TilePosition GetTilePositionFor(double x, double y)
        {
            var i = (int)(x >= (int)boardWidth ? (int)boardWidth - 1 : x) / TileWidth;
            var j = (int)(y >= (int)boardHeight ? (int)boardHeight - 1 : y) / TileHeight;
            return new TilePosition { I = (int)i, J = (int)j };
        }

        public void UpdatePieceMovement(TilePosition position)
        {
            // Invalid position?
            if (position == TilePosition.Default)
                return;

            // Unselect the current tile..?
            if (selectedTilePosition == position)
            {
                UnSelectTile(position);
                return;
            }

            // 
            if (CanSelectTile(position))
            {
                SelectTile(position);
                return;
            }

            // Select another tile?
            if (IsOpponent(position, this.PlayerColor))
            {
                UnSelectTile(selectedTilePosition);
                SelectTile(position);
                return;
            }

            if (CanMoveToTargetTile(position))
            {
                bool castling = false;
                bool userIntededToCastle = false;
                bool hasCurrentPieceMovedBefore = SelectedPiece.Moved;
                King kingOfSelectedPiece = KingOfSelectedPiece;

                // set piece on new tile
                Piece piece = MovePieceOnBoard(SelectedPiece, position);

                if (piece is King)
                {                    
                    castling = !hasCurrentPieceMovedBefore && Math.Abs(4 - piece.Position.I) > 1;
                    if (castling) // move Rook
                    {                        
                        bool queenSideCastling = piece.Position.I == 1;
                        if (queenSideCastling)
                            MovePieceOnBoard(boardState[0, piece.Position.J].Piece, new TilePosition { I = 2, J = piece.Position.J });
                        else
                            MovePieceOnBoard(boardState[7, piece.Position.J].Piece, new TilePosition { I = 5, J = piece.Position.J });
                    }
                }                    
                
//                if (piece is Rook)
//                {                    
//                    castling = userIntededToCastle && !kingOfSelectedPiece.Moved && !hasCurrentPieceMovedBefore && (piece.Position.I == 3 || piece.Position.I == 5);
//                    if (castling) // move PlayerKing
//                    {
//                        bool queenSideCastling = piece.Position.I == 2;
//                        if (queenSideCastling)
//                            MovePieceOnBoard(new TilePosition { I = 3, J = piece.Position.J });
//                        else
//                            MovePieceOnBoard(new TilePosition { I = 5, J = piece.Position.J });
//                    }                   
//                }                

                // clear highlight state
                ClearHighlightState();

                selectedTilePosition = TilePosition.Default;
            }
        }

        public bool IsTileEmpty(TilePosition position)
        {
            return boardState[position.I, position.J].Piece == null;
        }

        public bool IsOpponent(TilePosition position, PieceColor color)
        {
            return !IsTileEmpty(position) && boardState[position.I, position.J].Piece.Color != color;
        }

        private void ClearHighlightState()
        {
            foreach (var move in possibleMoves)
                boardState[move.I, move.J].Highlighted = false;
            possibleMoves = null;
        }

        public void UpdatePieceMovementTracking(TilePosition position)
        {
            if (possibleMoves != null)
            {
                // clear previous state
                foreach (var move in possibleMoves)
                {
                    bool onPath = move.I == position.I && move.J == position.J;
                    boardState[move.I, move.J].Highlighted = onPath;
                }
            }
        }
        
        public IEnumerable<TilePosition> GetTileRay(Piece piece, Direction direction, bool scanUntilPieceDetected)
        {
            // north: i = fixed, j = 0 -> s = 0, e = 0 --
            // north: j = 1 -> s = 1, e = 0 -- 
            // north: j = 7 -> s = 7, e = 0 --
            // south: j = 0 -> s = 0, e = 7 ++
            // south: j = 1 -> s = 1, e = 7 ++
            // south: j = 7 -> s = 7, e = 7 ++

            Func<int, bool> condition = null;
            Func<int, int> update = null;
            Func<int, int, TilePosition> create = null;
            int start = 0;
            switch (direction)
            {
                case Direction.East:
                    start = piece.Position.I + 1;
                    update = i => i + 1;
                    create = (i, j) => new TilePosition { I = i, J = piece.Position.J };
                    condition = i => i < MaxTiles;
                    break;
                case Direction.North:
                    start = piece.Position.J - 1;
                    update = i => i - 1;
                    create = (i, j) => new TilePosition { I = piece.Position.I, J = j };
                    break;
                case Direction.West:
                    start = piece.Position.I - 1;
                    update = i => i - 1;
                    create = (i, j) => new TilePosition { I = i, J = piece.Position.J };
                    break;
                case Direction.South:
                    start = piece.Position.J + 1;
                    update = i => i - 1;
                    create = (i, j) => new TilePosition { I = i, J = piece.Position.J };
                    break;
                case Direction.NorthWest:
                    start = piece.Position.I + 1;
                    update = i => i - 1;
                    create = (i, j) => new TilePosition { I = i, J = piece.Position.J };
                    break;
                case Direction.NorthEast:
                    start = piece.Position.I + 1;
                    update = i => i - 1;
                    create = (i, j) => new TilePosition { I = i, J = piece.Position.J };
                    break;
                case Direction.SouthEast:
                    update = i => i - 1;
                    create = (i, j) => new TilePosition { I = i, J = piece.Position.J };
                    break;
                case Direction.SouthWest:
                    update = i => i - 1;
                    create = (i, j) => new TilePosition { I = i, J = piece.Position.J };
                    break;
            }
            
            for (int index = start; condition(index); index = update(index))
            {
                var position = create(index, index);
                if (IsTileEmpty(position))
                {
                    yield return position;
                    continue;
                }

                if (scanUntilPieceDetected && IsOpponent(position, piece.Color))
                {
                    yield return position;
                }
                break;
            }
        }

        private bool IsLegalMove(TilePosition position)
        {
            if (possibleMoves == null)
                return false;

            return possibleMoves.Any(move => move.I == position.I && move.J == position.J);            
        }

        private void SelectTile(TilePosition position)
        {
            selectedTilePosition = position;
            boardState[position.I, position.J].Selected = true;
            possibleMoves = GetAllPossibleMovesOfSelectedPiece();
        }

        private void UnSelectTile(TilePosition position)
        {
            boardState[position.I, position.J].Selected = !boardState[position.I, position.J].Selected;
            selectedTilePosition = TilePosition.Default;
            ClearHighlightState();
        }

        private bool CanMoveToTargetTile(TilePosition position)
        {
            return (selectedTilePosition != TilePosition.Default) && IsLegalMove(position);
        }

        private IEnumerable<TilePosition> GetAllPossibleMovesOfSelectedPiece()
        {
            if (SelectedPiece != null)
                return SelectedPiece.GetAllPossibleMoves();

            return null;
        }

        private bool CanSelectTile(TilePosition position)
        {
            return boardState[position.I, position.J].Piece != null && selectedTilePosition == TilePosition.Default;
        }

        public Piece SelectedPiece
        {
            get
            {
                return selectedTilePosition != TilePosition.Default ? 
                    boardState[selectedTilePosition.I, selectedTilePosition.J].Piece : null;
            }
        }

        public King KingOfSelectedPiece
        {
            get { return SelectedPiece.Color == PieceColor.Black ? BlackKing : WhiteKing; }
        }

        public King BlackKing { get; set; }
        public King WhiteKing { get; set; }

        public void PlacePieceOnBoard(Piece piece, TilePosition position)
        {
            piece.Position = position;
            boardState[position.I, position.J].Piece = piece;

            if (piece is King)
                if (piece.Color == PieceColor.White)
                {
                    this.WhiteKing = (King) piece;
                }
                else
                {
                    this.BlackKing = (King) piece;
                }
        }        

        private Piece MovePieceOnBoard(Piece piece, TilePosition position)
        {
            boardState[piece.Position.I, piece.Position.J].Piece = null;
            boardState[piece.Position.I, piece.Position.J].Selected = false;
            boardState[position.I, position.J].Piece = piece;
            boardState[position.I, position.J].Highlighted = false;
            piece.Position = position;
            piece.Moved = true;
            return piece;
        }

        public bool IsOnBoard(TilePosition position)
        {
            return position.I >= 0 && position.I < MaxTiles && position.J >= 0 && position.J < MaxTiles;
        }
    }
}
